using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Camera
{

    public class Camera : GameComponent
    {

        Game game;
        CameraService service;

        public Camera(Game game) : base(game)
        {
            this.game = game;
            this.service = new CameraService(game.Window);
            this.game.Services.AddService(typeof(CameraService), this.service);
        }

        public override void Initialize()
        {

            System.Console.Out.WriteLine("Initializing library \"" + this.GetType().Name + "\"...");

            base.Initialize();
            System.Console.Out.WriteLine("Library  \"" + this.GetType().Name + "\" initialized");

        }
    }

    public class CameraService
    {

        Vector3 cameraPosition;
        Vector3 cameraTarget;
        Vector3 cameraUp;

        double rotationSin;
        double rotationCos;
        double radius;

        Matrix projection;

        private static CameraService service;

        public static CameraService getService()
        {
            if (CameraService.service == null) CameraService.service = new CameraService();
            return CameraService.service;
        }

        private CameraService()
        {

        }

        internal CameraService(GameWindow window) {
            this.Initialize(window);
        }

        public void Initialize(GameWindow window)
        {

            this.cameraPosition = new Vector3(0, 50, 70);
            this.cameraTarget = new Vector3(0, 0, 0);
            this.cameraUp = Vector3.Up;

            Vector3 targetDif = cameraPosition - cameraTarget;
            Vector2 camera = new Vector2(targetDif.X, targetDif.Z);
            radius = camera.Length();
            camera = Vector2.Normalize(camera);
            rotationCos = Math.Acos(camera.X);
            rotationSin = Math.Asin(camera.Y);

            float viewAngle = MathHelper.PiOver4;
            float aspectRatio = (float)window.ClientBounds.Width / (float)window.ClientBounds.Height;
            float nearPlane = 0.5f;
            float farPlane = 1000.0f;

            this.projection = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio, nearPlane, farPlane);

        }

        public void rotate(float leftRight)
        {

            Vector3 targetDif = cameraPosition - cameraTarget;

            rotationCos += leftRight;
            rotationSin += leftRight;
            double newX = Math.Cos(rotationCos);
            double newY = Math.Sin(rotationSin);

            Vector2 newCamera = new Vector2((float)newX, (float)newY) * (float)radius;

            Vector2 newDif = newCamera - new Vector2(targetDif.X, targetDif.Z);
            cameraPosition += new Vector3(newDif.X, 0, newDif.Y);
        }

        public void moveTo(Vector3 newCameraPosition)
        {

            Vector3 targetDif = cameraTarget - cameraPosition;

            cameraPosition = newCameraPosition;
            cameraTarget = newCameraPosition + targetDif;
        }

        public void moveForwardBack(float x)
        {
            Vector3 targetDif = cameraTarget - cameraPosition;
            Vector3 move = new Vector3(targetDif.X, 0.0f, targetDif.Z);
            this.moveTo(cameraPosition + (Vector3.Normalize(move) * x));
        }

        public void moveLeftRight(float x)
        {
            Vector3 targetDif = cameraTarget - cameraPosition;
            Vector3 move = Vector3.Cross(targetDif, cameraUp);
            this.moveTo(cameraPosition + (Vector3.Normalize(move) * x));
        }

        public void moveUpDown(int x)
        {
            Vector3 targetDif = cameraTarget - cameraPosition;
            Vector3 move = targetDif * cameraUp;
            this.moveTo(cameraPosition + (Vector3.Normalize(move) * x));
        }

        public Matrix getView()
        {
            return Matrix.CreateLookAt(cameraPosition, cameraTarget, cameraUp);
        }

        public Matrix getProjection()
        {
            return projection;
        }

        public Vector3 getCameraTarget()
        {
            return this.cameraTarget;
        }

    }
}
